import logging as log
import os
import shutil
import time

from avocado.utils import process

from virttest import data_dir
from virttest import libvirt_version
from virttest import utils_libvirtd
from virttest import utils_net
from virttest import virsh
from virttest.libvirt_xml import vm_xml
from virttest.utils_libvirt import libvirt_vmxml
from virttest.utils_libvirt import libvirt_network
from virttest.utils_test import libvirt


# Using as lower capital is not the best way to do, but this is just a
# workaround to avoid changing the entire file.
logging = log.getLogger('avocado.' + __name__)


def run(test, params, env):
    """
    Test autogenerated tap device name.

    1.Prepare test environment, restart libvirtd to flush the counter.
    2.start the domain.
    3.check the tap device name, then destroy the vm, restart libvirtd if
      needed, then start the vm again;
    4.hotplug an interface and check the tap device name;
    5.detach one interface, restart libvirtd if needed, then hotplug again to
      check the tap device name;
    6.hotplug interface of another type, check tap device name;
    7.recover the env.
    """
    def prepare_vmxml(vm, vm_name, direct=False):
        """
        Ensure there is only 1 requested interface in the vmxml

        param vm: the test vm
        param vm_name: the vm'name
        param direct: True or False, if True, prepare vm xml with a direct type
                      interface(the tap device will be named as macvtap*);
                      if False , prepare vm xml with a network type interface
                      connected to default network(the tap device will be named
                      as vnet* automatically instead)
        :return: None
        """
        libvirt_vmxml.remove_vm_devices_by_type(vm, 'interface')
        iface_dict = prepare_iface_dict(direct)
        libvirt.modify_vm_iface(vm_name, 'update_iface', iface_dict)

    def prepare_iface_dict(direct=False):
        """
        Prepare the iface_dict for the function 'libvirt.modify_vm_iface()' to use

        :param direct: True or False
        :return: a dictionary
        """
        if direct:
            iface_name = utils_net.get_net_if(state="UP")[0]
            source = "{'dev': '%s', 'mode': 'bridge'}" % iface_name
            iface_dict = {"model": "virtio", 'type': 'direct', 'source': source,
                          'del_addr': 'yes', 'del_alias': 'yes',
                          'del_target': 'yes'}
        else:
            iface_dict = {"model": "virtio", 'type': 'network',
                          'source': "{'network': 'default'}",
                          'del_addr': 'yes', 'del_alias': 'yes',
                          'del_target': 'yes'}
        return iface_dict

    def check_target_name(index, direct=False):
        """
        Check the auto generated tap device name on the host

        param index: an integer range in {-1,}, the max index occupied, if there
                    is no occupied, initial index is -1
        param direct: True or False.
                      True: the expected auto-generated tap device name should
                            be 'macvtap${index+1}'
                      False: the expected auto-generated tap device name should
                            be "vnet${index+1}"
        :return: None
        """
        cmd_output = process.run("ls /sys/class/net", shell=True,
                                 ignore_status=True).stdout_text
        logging.debug("Current network interfaces on the host includes: %s",
                      cmd_output)
        index_ = int(index)
        if direct:
            expected_name = 'macvtap' + str(index_ + 1)
        else:
            expected_name = "vnet" + str(index_ + 1)
        if expected_name not in cmd_output:
            test.fail("Can not get the expected tap: %s" % expected_name)
        else:
            logging.debug("Get the expected tap: %s" % expected_name)
        return

    libvirt_version.is_libvirt_feature_supported(params)
    vm_name = params.get("main_vm")
    vm = env.get_vm(vm_name)
    # Destroy VM first
    if vm.is_alive():
        vm.destroy(gracefully=False)

    test_macvtap = "yes" == params.get("test_macvtap", "no")
    flush_with_occupation = "yes" == params.get("flush_with_occupation", "no")
    flush_after_detach = "yes" == params.get("flush_after_detach", "no")

    # if there is existing vnet* or macvtap* even the test vm is destroyed,
    # the env is not clean, cancel the test
    cmd_output = process.run("ls /sys/class/net", shell=True,
                             ignore_status=True).stdout_text
    if ('vnet' in cmd_output and not test_macvtap) or \
            ('macvtap' in cmd_output and test_macvtap):
        test.cancel("The env is not clean, there is existing tap device!")
    # Back up xml file.
    vmxml_backup = vm_xml.VMXML.new_from_inactive_dumpxml(vm_name)
    libvirtd = utils_libvirtd.Libvirtd()
    try:
        prepare_vmxml(vm, vm_name, test_macvtap)
        libvirt_network.ensure_default_network()
        libvirtd.restart()
        # if there is no vm running, restart libvirtd will flush the counter
        # to the initial value: -1
        counter = -1
        vm.start()
        # check the auto generated tap device name after fresh initialized
        logging.debug("1. Check the tap device name after initialized:")
        check_target_name(counter, test_macvtap)
        # if the vm start successfully and tap device create successfully,
        # current counter should increase by 1
        counter = counter + 1
        # destroy and start the vm again
        vm.destroy()
        time.sleep(2)
        # flush when vm down, if flushed, the counter is initialized to -1
        if flush_with_occupation:
            libvirtd.restart()
            counter = -1
        time.sleep(2)
        vm.start()
        logging.debug("2. Check tap name after destroy and start vm again with "
                      "libvirtd restart: %s:", flush_with_occupation)
        check_target_name(counter, test_macvtap)
        # new tap created after vm start, counter increase by 1
        counter = counter + 1
        # add another interface with the same interface type
        if_dict = prepare_iface_dict(test_macvtap)
        mac_addr = utils_net.generate_mac_address_simple()
        if_dict['mac'] = mac_addr
        iface_add_xml = os.path.join(data_dir.get_data_dir(), "iface_add.xml")
        shutil.copyfile(libvirt.modify_vm_iface(vm_name, 'get_xml', if_dict), iface_add_xml)
        virsh.attach_device(vm_name, iface_add_xml, debug=True, ignore_status=False)
        time.sleep(2)
        logging.debug("3. Check tap name after hotplug an interface:")
        check_target_name(counter, test_macvtap)
        # one interface with same iface type attached, counter increase by 1
        counter = counter + 1
        # Make sure the guest boots up. Otherwise detach_device won't complete
        vm.wait_for_serial_login(timeout=180).close()
        # detach the new attached interface
        virsh.detach_device(vm_name, iface_add_xml, wait_for_event=True,
                            debug=True, ignore_status=False)
        if flush_after_detach:
            libvirtd.restart()
            # the latest occupied name is recycled after restart
            # the counter is not initialized to -1 as the vm is running and
            # first tap name is occupied
            counter = counter - 1
        virsh.attach_device(vm_name, iface_add_xml, debug=True, ignore_status=False)
        logging.debug("4 Check tap name after detach and reattach with "
                      "flushed: %s:", flush_after_detach)
        check_target_name(counter, test_macvtap)
    finally:
        if vm.is_alive():
            vm.destroy(gracefully=False)
        if "iface_add_xml" in locals():
            os.remove(iface_add_xml)
        vmxml_backup.sync()
